//*********************************************************************
//		Copyright (c) 2003 - 2008 Microsoft Corporation.
//      This code is provided under Microsoft Public License, which can be found at
//      http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx 
//
// @File: Collector.cs
//
// Purpose: Collects requested counters from a computerName
//
//
//
// Notes: 
//
//
//	
// History: 
//     @Version: V2.1
//
//     10/31/08 PGV 2.1.001.0 Starting V2.1 (based on 2.0.002.0 code)
//
//
// @EndHeader@
//*********************************************************************

using System;
using System.Collections;
using System.Diagnostics;
using System.Threading;
using System.ComponentModel;

namespace PerfService
{
    public struct ResultRecord
    {
        int computerId;
        int counterId;
        float counterValue;
        long collectionId;
        DateTime timestamp;

        public int ComputerId { get { return this.computerId; } set { this.computerId = value; } }
        public int CounterId { get { return this.counterId; } set { this.counterId = value; } }
        public float Value { get { return this.counterValue; } set { this.counterValue = value; } }
        public long CollectionId { get { return this.collectionId; } set { this.collectionId = value; } }
        public DateTime Timestamp { get { return this.timestamp; } set { this.timestamp = value; } }
    }

	public class CounterIdentity 
	{
        string obj;
        string counter;
        string instance;

        public string Object { get { return this.obj; } set { this.obj = value; } }
        public string Counter { get { return this.counter; } set { this.counter = value; } }
        public string Instance { get { return this.instance; } set { this.instance = value; } }

        public override bool Equals (object obj)
        {
            if (obj is CounterIdentity)
            {
                return Equals ((CounterIdentity)obj);
            }

            return false;
        }

        public bool Equals (CounterIdentity identity)
        {
            return this.Object.Equals (identity.Object) && this.Counter.Equals (identity.Counter) && this.Instance.Equals (identity.Instance);
        }
	}

	public enum CounterStateType
	{
		Uninitialized=0,
		Good,
		GoodSuspended, 
		Suspect,
		Junk
	}

	public class Counter : IDisposable
	{
		PerformanceCounter PfCounter;
		int counterId;
		bool isInitialized = false;
		DateTime wakeUpTime = DateTime.MinValue;
		ushort nRetry = 0;
		int retrySeconds = Constants.RetryIntervalSeconds;
		CounterIdentity identity;
		string computer;
		CounterStateType state = CounterStateType.Uninitialized;
        
        public CounterStateType State { get { return state; } }
        public int CounterId { get { return counterId; } }

		public Counter (string computer, CounterIdentity identity, int counterId)
		{
            this.counterId = counterId;
            this.identity = identity;
            this.computer = computer;
		}

		public override bool Equals (object obj)
		{
            Counter c = (Counter)obj;

			return (counterId == c.counterId && this.computer == c.computer); 
		}

        public override string ToString ()
        {
            return (identity.Object + ":" + identity.Counter + ":" + identity.Instance);
        }

        public string ToStringWithComputer ()
        {
            return (computer + " " + identity.Object + ":" + identity.Counter + ":" + identity.Instance);
        }

		void WriteDebug (int i)
		{
			Debug.WriteLine (DateTime.Now+","+computer+","+identity.Object+","+identity.Counter+","+identity.Instance+","+i+","
				+(int)state+","+nRetry+","+retrySeconds+","+wakeUpTime);

		}

		public void Init ()
		{
			if (this.nRetry == 0) SQLH2PerfCollector.Log ("Initializing "+ToStringWithComputer (),EventLogEntryType.Information, 99);
			PfCounter = new PerformanceCounter (identity.Object, identity.Counter, identity.Instance, computer);
			isInitialized = true;
		}

		public void Close ()
		{
			if (isInitialized)
			{
				PfCounter.Close ();
				isInitialized = false;
			}
		}

        public void Dispose ()
        {
            Dispose (true);
            GC.SuppressFinalize (this);
        }

        protected virtual void Dispose (bool disposing)
        {
            if (isInitialized)
            {
                if (disposing)
                {
                    Close ();
                }
            }
        }

		public void Collect (ResultRecord rr)
		{
			if ((state == CounterStateType.Good) || 
                (state == CounterStateType.Uninitialized) ||
				(wakeUpTime <= DateTime.Now)) 
			{
				if (!isInitialized) Init ();

				rr.CounterId = counterId;
				rr.Timestamp = DateTime.Now;
				rr.Value = PfCounter.NextValue ();
				PerfMgr.ResultQueue.Enqueue (rr);
				if (state != CounterStateType.Good)
				{
					if (state != CounterStateType.Uninitialized) 
					{
						SQLH2PerfCollector.Log (ToStringWithComputer ()+" resumes in 'normal' mode", EventLogEntryType.Information, 107);
					}
					state = CounterStateType.Good;
					nRetry = 0;
					retrySeconds = 0;
				}
			}
		}

		public void Win32Exception ()
		{
			Close ();
			WriteDebug (3);
		}


		public bool InvalidOperation (string message)
		{
			Close ();
			++nRetry;

			switch (state)
			{
				case CounterStateType.Uninitialized:
					SQLH2PerfCollector.Log ("Can't initialize counter "+ToStringWithComputer ()
						+Environment.NewLine+message, EventLogEntryType.Error, 31);
					SQLH2PerfCollector.Log (ToStringWithComputer ()+" switches into SUSPECT mode after failing to initialize", EventLogEntryType.Information, 104);
					state = CounterStateType.Suspect;
					break;
				case CounterStateType.Suspect:
					if (retrySeconds >= Constants.MaxRetryInterval) 
					{
						SQLH2PerfCollector.Log (ToStringWithComputer ()+" failed to resolve SUSPECT mode and will be removed from collection", EventLogEntryType.Information, 106);
						state = CounterStateType.Junk;
						WriteDebug (2);
						return true;
					}
					break;
				case CounterStateType.Good:
					SQLH2PerfCollector.Log (ToStringWithComputer ()+" encountered InvalidOperation error"
						+Environment.NewLine+message, EventLogEntryType.Error, 31);
					SQLH2PerfCollector.Log (ToStringWithComputer ()+" switches into GOOD_SUSPENDED mode", EventLogEntryType.Information, 105);
					state = CounterStateType.GoodSuspended;
					break;
			}


			if (nRetry == 1) retrySeconds = Constants.RetryIntervalSeconds;
			else if (retrySeconds <= Constants.MaxRetryInterval/2) retrySeconds *= 2;
			else retrySeconds = Constants.MaxRetryInterval;

			wakeUpTime = (DateTime.Now).AddSeconds (retrySeconds);

			WriteDebug (1);
			
			return false;
		}
	}

	public enum CollectorState
	{
		Idle=0,
		Active,
		Sleeping,
		Suspended,
		Deactivated
	}

	public class Collector
	{
		string computerName; 
		int computerId;
		ArrayList counters;
		bool isActive = true;
		ushort nRetry = 0;
		CollectorState State = CollectorState.Idle;
		long collectionId = 0;

		public Collector (string computer, int computerId)
		{
			this.computerName = computer;
			this.computerId = computerId;
			this.counters = new ArrayList ();
		}

		public void AddCounter  (CounterIdentity identity, int counterId)
		{
			Counter ctr;

			ctr = new Counter (computerName, identity, counterId);

            if (false == counters.Contains (ctr))
            {// Uniqueness check
                counters.Add (ctr);
            }
            else SQLH2PerfCollector.Log ("Duplicate entry: " + ctr.ToStringWithComputer () + Environment.NewLine +
                     "Ignoring the counter", EventLogEntryType.Warning, 252
                     );
		}

		public void CloseCounters ()
		{
			foreach (Counter c in counters)
			{
				c.Close ();
			}
		}

		public void FixSize ()
		{
			counters.TrimToSize ();
		}

		public void RemoveJunk ()
		{
			int i = 0;
			Counter ctr;

			while (i<counters.Count)
			{
				ctr = (Counter)counters[i];
				if (ctr.State == CounterStateType.Junk) counters.RemoveAt (i);
				else i++;
			}
			FixSize ();
		}

        public void Deactivate ()
        {
            isActive = false;
        }

		public void Collect ()
		{
			try 
			{
				ResultRecord rr;
				bool SuspendFlag = false,
					RemoveFlag = false;
				int RetrySeconds = Constants.RetryIntervalSeconds;

				string c_msg = computerName+" (comp_id="+computerId+"). ";

				if (counters.Count == 0) return;

                rr = new ResultRecord ();
				rr.ComputerId = computerId;

				while (isActive)
				{
					State = CollectorState.Active;
					collectionId++;
					rr.CollectionId = collectionId;

					foreach (Counter ctr in counters)
					{
						try
						{
							ctr.Collect (rr);
						}
						catch (InvalidOperationException oex)
						{
							RemoveFlag = ctr.InvalidOperation (oex.Message);
							continue;
						}
						catch (Win32Exception wex)
						{
							ctr.Win32Exception ();
							string msg = c_msg+"counter ("+ctr.CounterId+") "+ctr.ToString (); 
							SuspendFlag = true;
							State = CollectorState.Suspended;
							SQLH2PerfCollector.Log (ctr.ToStringWithComputer ()+" "+Environment.NewLine+wex.Message, EventLogEntryType.Error, 60);
							if (nRetry == 0) SQLH2PerfCollector.Log ("Collector on "+computerName+" switches into Retry mode after failing to obtain counter "+ctr.ToString (), EventLogEntryType.Information, 102);
							CloseCounters ();
							break;
						}
					}
					if (RemoveFlag)
					{
						RemoveJunk ();
						RemoveFlag = false;
						if (counters.Count == 0) 
						{
							SQLH2PerfCollector.Log ("No active counters left to collect on "+computerName+". Collector shuts down.", EventLogEntryType.Information, 101);
							return;
						}
					}
					if (SuspendFlag)
					{
						++nRetry;
						if (nRetry == 1) RetrySeconds = Constants.RetryIntervalSeconds;
						else if (RetrySeconds <= Constants.MaxRetryInterval/2) RetrySeconds *= 2;
						else RetrySeconds = Constants.MaxRetryInterval;

						Thread.Sleep (RetrySeconds * 1000); 
						SuspendFlag = false;
						continue;
					}

					// reaching this point must mean collection went well (was not suspended)
					if (nRetry > 0) 
					{	// returning from Retry mode
						SQLH2PerfCollector.Log ("Collection on "+computerName+" ("+computerId+") resumes in 'normal' mode", EventLogEntryType.Information, 101);
					}
					nRetry = 0;
					State = CollectorState.Sleeping;
					Thread.Sleep (1000 * Constants.CollectionIntervalSeconds);
				}
			}
			catch (Exception ex)
			{
				SQLH2PerfCollector.Log (ex.Message+" "+ex.StackTrace, EventLogEntryType.Error, 61); 
			}
				
		}


	}
}
